/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package polybook.model;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Random;
import java.util.Stack;
import polybook.utils.DatasFileUtils;
import polybook.utils.Log;
import polybook.utils.RandomUtils;

/**
 *
 * @author epulapp
 */
public class Person {

    /*
     * Valeur du pseudo de la personne
     */
    protected String pseudoPerson;
    /**
     * Valeur du nom de la personne
     */
    protected String nomPerson;
    /**
     * Valeur du prénom de la personne
     */
    protected String prenomPerson;
    /**
     * Valeur de la date de naissance de la personne
     */
    protected Date dateNaissPerson;
    /**
     * Valeur des centres d'intérêts de la personne
     */
    protected Hobbies hobbiesPerson;
    /**
     * Valeur de la liste d'amis de la personne
     */
    protected ArrayList<Person> friendListPerson;
    /*
     * Réseau dans lequel est contenue la personne.
     */
    protected Network myNetwork;
    // Constantes
    protected static final int MAX_HOBBIES_NUMBER = 5;
    protected static final int MIN_HOBBIES_NUMBER = 4;
    protected static ArrayList<String> dicFirstname;
    protected static ArrayList<String> dicLastname;

    public Person() {
    }

    /**
     * Constructeur personnalisé 1 : Création d'une personne avec paramètres.
     *
     * @param pseudoPerson : pseudo de la personne
     * @param nomPerson : nom de la personne
     * @param prenomPerson : prénom de la personne
     * @param dateNaissPerson : date de naissance de la personne
     * @param hobbiesPerson : liste des centres d'intérêts de la personne
     * @param friendListPerson : liste des amis de la personne
     */
    public Person(int idPerson, String pseudoPerson, String nomPerson, String prenomPerson, Date dateNaissPerson, Hobbies hobbiesPerson, ArrayList<Person> friendListPerson) {
        this.pseudoPerson = pseudoPerson;
        this.nomPerson = nomPerson;
        this.prenomPerson = prenomPerson;
        this.dateNaissPerson = dateNaissPerson;
        this.hobbiesPerson = hobbiesPerson;
        this.friendListPerson = friendListPerson;
    }

    /**
     * Raccourcis sur le constructeur aleatoire avec l'attribut myNetwork a null
     *
     * @throws IOException
     */
    public Person(String pseudo, boolean useDictionary) throws IOException {
        this(pseudo, useDictionary, null);
    }

    /**
     * Creation d'une personne de maniere aleatoire
     *
     * @param idPerson
     * @param pseudo Pseudo unique de la personne
     * @param useDictionary Definit si la methode doit utiliser les
     * dictionnaires
     * @param network Objet Network auquel la personne est rattachee
     * @throws IOException
     */
    public Person(String pseudo, boolean useDictionary, Network network) throws IOException {
        // Rattachement du reseaux
        myNetwork = network;

        // Chargement des dictionnaires
        if (dicFirstname == null || dicLastname == null) {
            dicFirstname = new ArrayList<String>(DatasFileUtils.load("DatasFiles/firstnames.properties"));
            dicLastname = new ArrayList<String>(DatasFileUtils.load("DatasFiles/lastnames.properties"));
        }

        // Initialisation d'un objet random pour les index de dictionnaires
        Random randomObject = new Random();

        // Nombre de hobbie aléatoire, génération des index pour les dictionnaires
        int hobbiesNumber = randomObject.nextInt(MAX_HOBBIES_NUMBER - MIN_HOBBIES_NUMBER) + MIN_HOBBIES_NUMBER;
        int firstnameIndex = randomObject.nextInt(dicFirstname.size());
        int lastnameIndex = randomObject.nextInt(dicLastname.size());

        this.pseudoPerson = pseudo;
        if (useDictionary) {
            this.prenomPerson = dicFirstname.get(firstnameIndex);
            this.nomPerson = dicLastname.get(lastnameIndex);
        } else {
            this.nomPerson = RandomUtils.generateString(5, 10, false);
            this.prenomPerson = RandomUtils.generateString(5, 10, false);
        }
        this.dateNaissPerson = RandomUtils.generateDate();
        this.hobbiesPerson = new Hobbies(hobbiesNumber, useDictionary);
        this.friendListPerson = new ArrayList<Person>();
    }

    //Accesseurs
    public Date getDateNaiss() {
        return dateNaissPerson;
    }

    public void setDateNaiss(Date dateNaissPerson) {
        this.dateNaissPerson = dateNaissPerson;
    }

    public ArrayList<Person> getFriendList() {
        return friendListPerson;
    }

    public void setFriendList(ArrayList<Person> friendListPerson) {
        this.friendListPerson = friendListPerson;
    }

    public Hobbies getHobbies() {
        return hobbiesPerson;
    }

    public void setHobbies(Hobbies hobbiesPerson) {
        this.hobbiesPerson = hobbiesPerson;
    }

    public String getNom() {
        return nomPerson;
    }

    public void setNom(String nomPerson) {
        this.nomPerson = nomPerson;
    }

    public String getPrenom() {
        return prenomPerson;
    }

    public void setPrenom(String prenomPerson) {
        this.prenomPerson = prenomPerson;
    }

    public String getPseudo() {
        return pseudoPerson;
    }

    public void setPseudo(String pseudoPerson) {
        this.pseudoPerson = pseudoPerson;
    }

    @Override
    public String toString() {
        String person = "Person " + prenomPerson + " " + nomPerson + " {" + "pseudoPerson="
                + pseudoPerson + ", nomPerson=" + nomPerson + ", prenomPerson="
                + prenomPerson + ", dateNaissPerson=" + dateNaissPerson
                + ", hobbiesPerson=" + hobbiesPerson
                + "\n----LISTE D'AMIS----\n";
        int i = 0;
        Person onePerson;
        for (Person onePerson1 : this.friendListPerson) {

            person += "  * Nom Person: "
                    + onePerson1.prenomPerson + " "
                    + onePerson1.nomPerson + "\n";
            i++;
        }
        person += "Nb of friend :" + i + "\n";
        return person;
    }
    
    /**
     * Calcul la probabilité pour cette personne. La probabilité représente
     * la chance qu'a une personne arrivant sur ce réseau d'être amis avec
     * cette personne.
     * 
     * @return float - La probabilité.
     */
    public float calculProba() {
        float result = ((float) 5 * this.friendListPerson.size())
                / (float) this.myNetwork.nbLink;
        return result;
    }

    public void setNetwork(Network network) {
        this.myNetwork = network;
    }

    public Network getNetwork() {
        return this.myNetwork;
    }
    
    /**
     * Lie cette personne avec la personne passée en paramètre.
     * @param friend 
     */
    public void linkTo(Person friend) {
        this.friendListPerson.add(friend);
        friend.friendListPerson.add(this);
    }

    public void unlinkTo(Person friend) {
        this.friendListPerson.remove(friend);
        friend.friendListPerson.remove(this);
    }

    public void unlinkAll() {
        ArrayList<Person> temp = (ArrayList<Person>) this.friendListPerson.clone();
        for (Person friend : temp) {
            this.unlinkTo(friend);
        }
    }

    /**
     * Algorithme de recherche de personne dans les amis d'amis (donc dans un
     * arbre) avec un parcours en largeur (BFS).
     *
     * @param pseudo Pseudo de la personne à chercher
     * @return L'objet Person de la personne à chercher, null si aucune personne
     * trouvé dans le réseau
     */
    public Person searchByPseudo_BFS(String pseudo) {
        // Initialisation de la pile et des iterateurs
        ArrayList<Person> markedPerson = new ArrayList<Person>();
        Stack<Person> baseStack = new Stack<Person>();
        Person itBaseStack;
        
        // Initialisation du marquage
        baseStack.push(this);
        
        // Case particulier : Se cherche lui même
        if (this.pseudoPerson.compareTo(pseudo) == 0) {
            return this;
        }
        
        // Première boucle
        while (!baseStack.isEmpty()) {
            // On recupere le prochain candidat sur la liste
            itBaseStack = baseStack.pop();
            Log.v("Main loop on " + itBaseStack.getPseudo());
            Log.v("Main loop size " + baseStack.size());
            // On scanne ses enfants (BFS : pour parcours en largeur)
            for (Person itChilds : itBaseStack.friendListPerson) {
                // On pose une sécurité pour les graphes circulaires
                // pour ne pas marquer 2 fois le même objet
                if (!markedPerson.contains(itChilds)) {
                    if (itBaseStack.pseudoPerson.compareTo(pseudo) == 0) {
                        return itBaseStack;
                    } else {
                        markedPerson.add(itBaseStack);
                    }
                    Log.v("Second loop, add " + itChilds.getPseudo() + " to main");
                    baseStack.push(itChilds);
                }
            }
        }
        return null;
    }

    public Person searchByPseudo_DFS(String pseudo) {
        searchByHobbies_shared = new ArrayList<Person>();
        Person result = searchByPseudo_DFS_recursive(this, pseudo);
        searchByHobbies_shared = null;
        return result;
    }

    protected Person searchByPseudo_DFS_recursive(Person itPerson, String pseudo) {
        // Initialisation des marqueurs
        searchByHobbies_shared.add(itPerson);
        for (Person itChilds : itPerson.getFriendList()) {
            // Si la bonne personne est trouve, on la retourne
            if (itChilds.pseudoPerson.compareTo(pseudo) == 0) {
                return itChilds;
            }
            // Si le noeud n'a pas encore ete parcourus, on le parcours
            if (!searchByHobbies_shared.contains(itChilds)) {
                return searchByPseudo_DFS_recursive(itChilds, pseudo);
            }
        }
        // Tous les noeud ont ete parcourus ont retourne null
        return null;
    }
    /*
     * Resource utilisé dans la methode recursive de la recherche DFS de hobbies
     * communs
     */
    private ArrayList<Person> searchByHobbies_DFS_Results;
    private ArrayList<Person> searchByHobbies_shared;

    /**
     * Methode de parcours DFS retournant une liste de personnes ayant le même
     * nombre de hobbies commun avec la personne courante
     *
     * @param sameHobbies Nombre de hobbies communs requis
     * @return Liste des personnes ayant le même nombre de hobbies communs
     */
    public ArrayList<Person> searchByHobbies_DFS(int sameHobbies) {
        Log.v("Recherche " + sameHobbies + " similaire...");
        searchByHobbies_DFS_Results = new ArrayList<Person>();
        searchByHobbies_shared = new ArrayList<Person>();
        if(this.hobbiesPerson.getContainer().size() < sameHobbies) {
            return searchByHobbies_DFS_Results;
        }
        this.searchByHobbies_DFS_recursive(this, this, sameHobbies);
        searchByHobbies_shared = null;
        return searchByHobbies_DFS_Results;
    }

    /**
     * Methode de recherche recursive
     *
     * @param itPerson Noeud courant à parcourir
     * @param sameHobbies Nombre de hobbies communs requis
     * @return Une liste de personne
     */
    protected void searchByHobbies_DFS_recursive(Person itPerson, Person basePerson, int sameHobbies) {
        // Marquage de l'objet courant
        searchByHobbies_shared.add(itPerson);

        // Traitement de l'objet courant
        int hobbiesEquals = 0;
        for (String currentHobbie : itPerson.hobbiesPerson.getContainer()) {
            if (basePerson.hobbiesPerson.getContainer().contains(currentHobbie)) {
                hobbiesEquals++;
            }
        }
        if (hobbiesEquals == sameHobbies) {
            this.searchByHobbies_DFS_Results.add(itPerson);
        }

        // Traitement de ses enfants
        for (Person itChilds : itPerson.getFriendList()) {
            // Si le noeud n'a pas encore ete parcourus, on le parcours
            if (!searchByHobbies_shared.contains(itChilds)) {
                this.searchByHobbies_DFS_recursive(itChilds, basePerson, sameHobbies);
            }
        }
    }

    /**
     * Algorithme de recherche de personne a partir du nombre de hobbies commun
     *
     * @param person Personne a partir de laquelle chercher
     * @param sameHobbies Nombre de hobbies commun requis
     * @return Liste des peronnes ayant le même nombre de hobbies
     */
    public ArrayList<Person> searchByHobbies_BFS(int sameHobbies) {
        // Resultat
        ArrayList<Person> result = new ArrayList<Person>();
        int hobbiesEquals;
        
        // Securité
        if(this.hobbiesPerson.getContainer().size() < sameHobbies) {
            return result;
        }
        
        // Initialisation de la pile et des iterateurs
        ArrayList<Person> markedPerson = new ArrayList<Person>();
        Stack<Person> baseStack = new Stack<Person>();
        Person itBaseStack;
        // Initialisation du marquage
        baseStack.push(this);
        markedPerson.add(this);

        // Première boucle
        while (!baseStack.isEmpty()) {
            // On recupere le prochain candidat sur la liste et on le marque
            itBaseStack = baseStack.pop();

            Log.v("Main loop on " + itBaseStack.getPseudo());
            Log.v("Main loop size " + baseStack.size());

            // On traite l'objet courant
            
            hobbiesEquals = 0;
            for (String currentHobbie : itBaseStack.hobbiesPerson.getContainer()) {
                if (this.getHobbies().getContainer().contains(currentHobbie)) {
                    hobbiesEquals++;
                }
            }
            if (hobbiesEquals == sameHobbies) {
                result.add(itBaseStack);
            }

            // On scanne ses enfants (BFS : pour parcours en largeur)
            for (Person itChilds : itBaseStack.friendListPerson) {
                // On pose une sécurité pour les graphes circulaires
                // pour ne pas marquer 2 fois le même objet
                if (!markedPerson.contains(itChilds)) {
                    markedPerson.add(itChilds);
                    Log.v("Second loop, add " + itChilds.getPseudo() + " to main");
                    baseStack.push(itChilds);
                }
            }
        }
        return result;
    }
}
